In [1]:
import numpy as np
import pandas as pd
import seaborn as sn
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import xgboost as xgb
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import mean_squared_error
from sklearn.metrics import explained_variance_score
import matplotlib.pyplot as plt
import lime
import lime.lime_tabular

Wykorzystałem zbiór danych z PD1. Początek jest zatem analogiczny. Wczytanie danych, sprawdzenie czy są braki i sprawdzenie typów danych

In [131]:
data=pd.read_csv("kc_house_data.csv")
data.head()

print('braki: ',data.isnull().values.any())
data.dtypes
braki:  False
Out[131]:
id                 int64
date              object
price            float64
bedrooms           int64
bathrooms        float64
sqft_living        int64
sqft_lot           int64
floors           float64
waterfront         int64
view               int64
condition          int64
grade              int64
sqft_above         int64
sqft_basement      int64
yr_built           int64
yr_renovated       int64
zipcode            int64
lat              float64
long             float64
sqft_living15      int64
sqft_lot15         int64
dtype: object

Zmienne dla których nie ma sensu liczyć statystyk tj. średniej przetwarzam na zmienne kategorialne.

In [132]:
# zmienne kategorialne - konwersja

data.condition = pd.Categorical(data.condition)
data.bedrooms = pd.Categorical(data.bedrooms)
data.floors = pd.Categorical(data.floors)
#data.waterfront = pd.Categorical(data.waterfront)
data.view = pd.Categorical(data.view)
data.zipcode = pd.Categorical(data.zipcode)

Sprawdzam rozpiętość dat. Jest niewielka ~rok, więc usuwam tę kolumnę.

In [133]:
data.date=pd.to_datetime(data['date'])
max(data.date)-min(data.date)
#rozstęp dat około rok - można usunąć kolumnę
data=data.drop(columns=['id','date'])

Sprawdzenie korelacji zmiennych - najwyższa około 0.8

Przygotowanie danych pod XGboost

In [134]:
condition = pd.get_dummies(data.condition, prefix = 'condition')
bedrooms = pd.get_dummies(data.bedrooms, prefix = 'bedrooms')
floors = pd.get_dummies(data.floors, prefix = 'floors')
view = pd.get_dummies(data.view, prefix = 'view')
#zipcode = pd.get_dummies(data.zipcode, prefix = 'category')

data = pd.concat([data, condition, bedrooms, floors, view], axis=1)
data=data.drop(columns=['condition','bedrooms','floors','view','zipcode'])

Podział przygotowanego zbioru pod XGB

In [136]:
y = data.price
X = data.drop(['price'], axis=1).select_dtypes(exclude=['object'])
train_X, test_X, train_y, test_y = train_test_split(X.values, y.values, test_size=0.25)

Zadanie 1: Zastosowanie modelu

In [137]:
model = xgb.XGBRegressor(objective='reg:squarederror',booster="gbtree",n_estimators=100, learning_rate=0.08, gamma=0, subsample=0.75, colsample_bytree=1, max_depth=7)
model.fit(X_train, y_train)
C:\ProgramData\Anaconda3\lib\site-packages\xgboost\core.py:587: FutureWarning: Series.base is deprecated and will be removed in a future version
  if getattr(data, 'base', None) is not None and \
C:\ProgramData\Anaconda3\lib\site-packages\xgboost\core.py:588: FutureWarning: Series.base is deprecated and will be removed in a future version
  data.base is not None and isinstance(data, np.ndarray) \
Out[137]:
XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
             colsample_bynode=1, colsample_bytree=1, gamma=0,
             importance_type='gain', learning_rate=0.08, max_delta_step=0,
             max_depth=7, min_child_weight=1, missing=None, n_estimators=100,
             n_jobs=1, nthread=None, objective='reg:squarederror',
             random_state=0, reg_alpha=0, reg_lambda=1, scale_pos_weight=1,
             seed=None, silent=None, subsample=0.75, verbosity=1)
In [138]:
preds = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, preds))
print("RMSE: %f" % (rmse))
RMSE: 113275.916236

Zadanie 2 i 3

In [139]:
explainer = lime.lime_tabular.LimeTabularExplainer(train_X, feature_names=X.columns, class_names=['price'], verbose=True, mode='regression')

Zadanie 4: wykresy poszczególnych obserwacji. Dla poniższych obserwacji można zauważyć: brak widoku na morze zawsze jest cechą negatywną, a jego obecność - pozytywną. Szerokość geograficzna niejako "klasteryzuje" dane. Wartość <=47.47 wpływa negatywnie, natomiast w przedziale 47.57 < lat <= 47.68 pozytywnie. Intuicyjnie i zgodnie z oczekiwaniami działa również zmienna "grade": wysoka podnosi cenę, niska obniża.

In [140]:
exp = explainer.explain_instance(test_X[2], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 902343.2604500357
Prediction_local [178887.33161746]
Right: 202891.66
In [141]:
exp = explainer.explain_instance(test_X[100], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 1012690.048209461
Prediction_local [256679.37715519]
Right: 213345.16
In [142]:
np.unique(test_X[:,3])
np.where(test_X[:,3]==1)
Out[142]:
(array([ 121,  218,  764,  948, 1166, 1183, 1267, 1279, 1395, 1401, 1426,
        1886, 1966, 2186, 2456, 2518, 2604, 2667, 2739, 2761, 2762, 2817,
        3132, 3324, 3393, 3435, 3568, 3639, 3801, 3824, 3858, 4075, 4149,
        4165, 4192, 4264, 4356, 4363, 4388, 4770, 5235, 5287, 5362, 5385],
       dtype=int64),)
In [144]:
exp = explainer.explain_instance(test_X[121], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 242337.05862691998
Prediction_local [1428709.92296942]
Right: 2917974.8
In [145]:
np.unique(test_X[:,9])
np.where(test_X[:,9]<47.47)
Out[145]:
(array([   2,    4,   12, ..., 5396, 5397, 5402], dtype=int64),)
In [146]:
exp = explainer.explain_instance(test_X[2], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 859177.0932771382
Prediction_local [203939.82449298]
Right: 202891.66
In [147]:
exp = explainer.explain_instance(test_X[4], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 995043.267813104
Prediction_local [255884.5804153]
Right: 195646.9

Zadanie 5: Regresja liniowa

In [148]:
regressor = LinearRegression()
regressor.fit(X_train,y_train)
Out[148]:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

Powtórzenie wykresu dla modelu XGBoost (dla porównania)

In [165]:
exp = explainer.explain_instance(test_X[9], model.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 482778.5319639052
Prediction_local [468855.92358895]
Right: 418067.9

Wyjaśnienie dla regresji liniowej

In [168]:
exp = explainer.explain_instance(test_X[9], regressor.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 1537834.5264258203
Prediction_local [528129.36406302]
Right: 463588.5379608348

Komentarz: Rozkład znaczących cech jest zgoła odmienny dla tej obserwacji w dwóch modelach. Dotyczy to głównie ich wagi (w modelu XGBoost najbardziej negatywną cechą jest "grade", natomiast w modelu liniowym jest ona dopiero czwartym co do wielkości czynnikiem.

In [150]:
exp = explainer.explain_instance(test_X[4], regressor.predict, num_features=10)
exp.show_in_notebook(show_table=False)
Intercept 1602919.139461502
Prediction_local [221519.48898086]
Right: 46782.26372847706

Dla powyższej obserwacji (indeks numer 4) można powtórzyć w zasadzie to, co było napisane powyżej. Zmienia się kolejność zmiennych ze względu na ich ważność, ale najbardziej znaczące z nich mają tę samą tendencję - zawsze negatywną bądź zawsze pozytywną.

Dla danych cen domów z King County i techniki Lime wyniki obu modeli są dość zbieżne, to znaczy dana cecha ma zwykle ten sam rodzaj wpływu.